package org.tools.wstt.servlet;

import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;

/**
 * Servlet implementation class ConnectionProbeServlet.
 */
public class ConnectionProbeServlet extends AHttpServlet {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;

	/**
	 * Do get.
	 * 
	 * @param pRequest
	 *            the request
	 * @param pResponse
	 *            the response
	 * @throws ServletException
	 *             the servlet exception
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest pRequest,
			HttpServletResponse pResponse) throws ServletException, IOException {

		if (!StringUtils.isEmpty(pRequest.getParameter("host"))
				&& !StringUtils.isEmpty(pRequest.getParameter("port"))) {
			String vHost = pRequest.getParameter("host");
			Integer vPort = Integer.valueOf(pRequest.getParameter("port"));

			// timeout
			Long vTimeout = APP_PROBE_DEF_TIMEOUT_MS;
			if (!StringUtils.isEmpty(pRequest.getParameter("timeout"))) {
				vTimeout = Long.valueOf(pRequest.getParameter("timeout"));
			}

			// call probing
			Map vProbeResultMap = probe(vHost, vPort, vTimeout);

			// save probe result
			List vList = (List) mContext.getAttribute(APP_PROBE_RESULTS);
			if (vList.size() >= APP_PROBE_REMIND_CALLS) {
				vList.remove(0);
			}
			vList.add(vProbeResultMap);

			// make output
			boolean vOutputDone = generateOutput(pRequest, pResponse, vProbeResultMap);
			// no output done
			if(!vOutputDone) {
				pResponse.sendRedirect(URI_TOOLS + "/probe_remote");
			}

		} else {
			pResponse.sendError(HttpServletResponse.SC_PRECONDITION_FAILED,
					"host and port parameters are required.");
		}
	}

	/**
	 * Do post.
	 *
	 * @param pRequest the request
	 * @param pResponse the response
	 * @throws ServletException the servlet exception
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
	 * javax.servlet.http.HttpServletResponse)
	 */
	protected void doPost(HttpServletRequest pRequest,
			HttpServletResponse pResponse) throws ServletException, IOException {
		this.doGet(pRequest, pResponse);
	}

	/**
	 * Probe logic.
	 * 
	 * @param pHost
	 *            the host
	 * @param pPort
	 *            the port
	 * @param pTimeout
	 *            the timeout
	 * @return the status
	 */
	protected Map probe(String pHost, Integer pPort, Long pTimeout) {
		int vStatus = APP_PROBE_STATUT_KO;
		long vElapsedTime = -1;

		try {
			long vStart = System.currentTimeMillis();
			Socket vSock = new Socket();
			vSock.bind(null);
			log("Connect to " + pHost + ":" + pPort + " - timeout : "
					+ pTimeout + "ms");
			vSock.connect(new InetSocketAddress(pHost, pPort.intValue()),
					pTimeout.intValue());
			vStatus = APP_PROBE_STATUT_OK;
			vSock.close();
			long vEnd = System.currentTimeMillis();
			vElapsedTime = vEnd - vStart;
			log("Connecting ended - time elapsed : " + (vEnd - vStart) + "ms");
		} catch (SocketTimeoutException vE) {
			logError("Error Timeout : " + vE.getMessage(), vE);
		} catch (ConnectException vE) {
			if ("Connection refused: connect".equals(vE.getMessage())) {
				logError("Connection refused : " + vE.getMessage(), vE);
				vStatus = APP_PROBE_STATUT_NO_SERVICE;
			} else {
				logError("Error Connecting : " + vE.getMessage(), vE);
			}
		} catch (IOException vE) {
			if ("already connected".equals(vE.getMessage())) {
				logError("Already connected : " + vE.getMessage(), vE);
				vStatus = APP_PROBE_STATUT_OK;
			} else {
				logError("Erreur : " + vE.getMessage(), vE);
			}
		}
		Map vResults = new HashMap();
		vResults.put("status", new Integer(vStatus));
		vResults.put("date", GregorianCalendar.getInstance().getTime());
		vResults.put("host", pHost);
		vResults.put("port", pPort);
		vResults.put("time", "" + vElapsedTime);
		return vResults;
	}

	/**
	 * Generate output.
	 *
	 * @param pRequest the request
	 * @param pResponse the response
	 * @param pProbeResult the probe result
	 * @return true, if successful
	 * @throws ServletException the servlet exception
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	protected boolean generateOutput(HttpServletRequest pRequest,
			HttpServletResponse pResponse, Map pProbeResult)
			throws ServletException, IOException {
		boolean vOutputDone = false;
		
		// format alternatives ?
		if (!StringUtils.isEmpty(pRequest.getParameter("alt"))) {
			StringBuffer vResponseBuff = new StringBuffer();
			String vContentType = "text/plain";
			if ("json".equals(pRequest.getParameter("alt"))) {
				boolean vCallback = false;
				vContentType = "application/json";
				// JSONP callback ?
				if (!StringUtils.isEmpty(pRequest.getParameter("callback"))) {
					vCallback = true;
					vContentType = "text/javascript";
					vResponseBuff.append(pRequest.getParameter("callback"));
					vResponseBuff.append("(");
				}
				vResponseBuff.append("{\"status\": \"");
				vResponseBuff.append(pProbeResult.get("status"));
				vResponseBuff.append("\"}");
				if (vCallback) {
					vResponseBuff.append(")");
				}
			} else {
				// just as text
				vResponseBuff.append("status=");
				vResponseBuff.append(pProbeResult.get("status"));
			}
			pResponse.setContentType(vContentType);
			pResponse.getOutputStream().write(
					vResponseBuff.toString().getBytes());
			vOutputDone = true;
		}
		return vOutputDone;
	}
}
